/**
 * 
 */
package com.android.vocalNotes;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Base64;
import android.util.Base64OutputStream;

/**
 * VocalNotes class provide a public API for clients that want to use the
 * VocalNotesContentProvider, although having no dependencies on its
 * implementation.
 * 
 * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
 */
public class VocalNotes {

   /**
    * The scheme indicates that data is controlled by a content provider. It is
    * useful for content resolvers that in this way know they must find a
    * content provider to resolve the URI.
    */
   public static final String SCHEME = "content://";

   /**
    * The authority part of the URI identifies the content provider. It must be
    * unique so content resolvers can find it.
    */
   public static final String AUTHORITY = "com.android.vocalNotes.VocalNotes";

   /**
    * The path part of the URI identifies the resource that this content
    * provider manages. It must be unique so that the content provider can
    * understand which exact data to manage. This path identifies the vocal
    * notes resource.
    */
   public static final String PATH_NOTES = "Notes";

   /**
    * The path part of the URI identifies the resource that this content
    * provider manages. It must be unique so that the content provider can
    * understand which exact data to manage. This path identifies the JSON file
    * (.json) resource.
    */
   public static final String PATH_JSON = "Json";

   /**
    * The entire content: URI that uniquely identifies the data set.
    * Applications pass this URI to content resolvers to access the content
    * provider methods for accessing to the vocal notes resource.
    */
   public static final Uri CONTENT_URI_NOTES = Uri.parse(SCHEME + AUTHORITY
         + "/" + PATH_NOTES);

   /**
    * The entire content: URI that uniquely identifies the data set.
    * Applications pass this URI to content resolvers to access the content
    * provider methods for accessing the JSON file resource.
    */
   public static final Uri CONTENT_URI_JSON = Uri.parse(SCHEME + AUTHORITY
         + "/" + PATH_JSON);

   /*
    * Definition of the fields of the JSON file.
    */

   /**
    * The name of the field in the JSON file that contains the identifier of
    * this application.
    */
   public static final String OBJECT_APP_NAME = "VOCALNOTES";

   /**
    * The value of the field in the JSON file that identify this application. It
    * is used to prevent the user from opening wrong JSON file with the
    * application.
    */
   public static final String OBJECT_APP_NAME_VALUE = "DC88GM84MK84";

   /**
    * The name of the field in the JSON file that contains the title of a note.
    */
   public static final String OBJECT_NOTE_TITLE = "Title";

   /**
    * The name of the field in the JSON file that contains the date of a note.
    */
   public static final String OBJECT_NOTE_DATE = "Date";

   /**
    * The name of the field in the JSON file that contains the description of a
    * note.
    */
   public static final String OBJECT_NOTE_DESCR = "Description";

   /**
    * The name of the field in the JSON file that contains the rating of a note.
    */
   public static final String OBJECT_NOTE_RATE = "Rating";

   /**
    * The name of the field in the JSON file that contains the audio of a note.
    */
   public static final String OBJECT_NOTE_AUDIO = "Audio";

   /*
    * Definition of the MIME types:
    */

   /**
    * The MIME type of {@link #CONTENT_URI_NOTES} providing a directory of vocal
    * notes.
    */
   public static final String NOTES_CONTENT_TYPE = "vnd.android.cursor.dir/vnd.vocalnotes.note";

   /**
    * The MIME type of {@link #CONTENT_URI_NOTES} providing a single item of
    * vocal notes.
    */
   public static final String NOTES_CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.vocalnotes.note";

   /**
    * The MIME type of {@link #CONTENT_URI_JSON} providing a directory of JSON
    * files.
    */
   public static final String JSON_CONTENT_TYPE = "vnd.android.cursor.dir/vnd.vocalnotes.json";

   /**
    * The MIME type of {@link #CONTENT_URI_JSON} providing a single item of JSON
    * files.
    */
   public static final String JSON_CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.vocalnotes.json";

   /**
    * This class cannot be instantiated. It represents a public API for clients
    * that want to use the VocalNotesContentProvider. It provides some constant
    * strings the clients use to access column fields and data.
    * 
    * In addiction to those declared, by implementing BaseColumns, the API also
    * exposes _ID and _COUNT columns.
    * 
    * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
    */
   public static final class VocalNotesContentProviderAPI implements
         BaseColumns {

      /*
       * Definition of the columns:
       */

      /**
       * The name of the table that stores the vocal notes.
       */
      public static final String NOTES_TABLE_NAME = "Notes";

      /**
       * The name of the title column.
       */
      public static final String COLUMN_NAME_TITLE = "title";

      /**
       * The name of the date column.
       */
      public static final String COLUMN_NAME_DATE = "date";

      /**
       * The name of the stars column.
       */
      public static final String COLUMN_NAME_RATING = "rating";

      /**
       * The name of the description file column.
       */
      public static final String COLUMN_NAME_DESCRIPTION = "description";

      /**
       * The name of the field in which storing the extension of the audio file.
       */
      public static final String FILE_EXTENSION = "extension";

      /**
       * The name of the table that stores the JSON file URL.
       */
      public static final String JSON_TABLE_NAME = "Json";

      /*
       * Useful SQL command parts:
       */

      /**
       * A set of column names to be the default projection in query() calls.
       */
      public static final String[] PROJECTION = new String[] { _ID,
            COLUMN_NAME_TITLE, COLUMN_NAME_DATE, COLUMN_NAME_RATING,
            COLUMN_NAME_DESCRIPTION };

      /**
       * A set of column names to be the simplified projection in query() calls.
       * It projects the COLUN_NAME_TITLE, COLUMN_NAME_DATE and
       * COLUMN_NAME_RATING constants.
       */
      public static final String[] SIMPLE_PROJECTION = new String[] {
            COLUMN_NAME_TITLE, COLUMN_NAME_DATE, COLUMN_NAME_RATING };

      /**
       * Column that determines the sort order of the results. Order by
       * {@link #COLUMN_NAME_DATE}.
       */
      public static final String ORDER_BY_DATE = COLUMN_NAME_DATE + " DESC, "
            + COLUMN_NAME_TITLE;

      /**
       * Column that determines the sort order of the results. Order by
       * {@link #COLUMN_NAME_RATING}.
       */
      public static final String ORDER_BY_RATING = COLUMN_NAME_RATING
            + " DESC, " + ORDER_BY_DATE;

      /**
       * Column that determines the default sort order of the results. Order by
       * {@link #ORDER_BY_DATE}.
       */
      public static final String DEFAULT_ORDER = ORDER_BY_DATE;
   }

   /**
    * Public static method that returns the content: URI of the JSON file. Use
    * this method for obtaining the unique JSOn file URI used for sending a
    * vocal note. It is necessary and recommended.
    * 
    * @param resolver
    *           The ContentResolver to be queried.
    * @return The content: URI of the JSON file.
    * @throws FileNotFoundException
    *            Thrown if the URI corresponds to many items.
    */
   public static Uri getJsonURI(ContentResolver resolver)
         throws FileNotFoundException {
      // get the Cursor of the JSON file
      Cursor cursor = resolver
            .query(CONTENT_URI_JSON,
                  new String[] { VocalNotesContentProviderAPI._ID }, null,
                  null, null);

      // construct the content: URI or throw an exception
      if (cursor.moveToFirst()) {
         if (cursor.isLast()) {
            // if the Cursor has only one row

            // get the index of the _ID column
            int index = cursor.getColumnIndex(VocalNotesContentProviderAPI._ID);

            // get the row ID
            long id = cursor.getLong(index);

            // then return the content: URI with the appended ID
            return ContentUris.withAppendedId(CONTENT_URI_JSON, id);

         } else {
            // else (it has more than one row) throw an exception
            throw new FileNotFoundException(
                  "ContentResolver found too many resources for the JSON file.");
         }
      } else {
         // else (it hasn't any row) insert the row into the table and return
         // its URI

         /*
          * Note that this call should not throw an SQLException in the
          * tentative of the insertion of a _data URL already inserted, because
          * in this case the query() call should have returned a cursor with at
          * least one row and hence this section of the if-then-else should not
          * be executed.
          */

         return resolver.insert(CONTENT_URI_JSON, null);
      }
   }

   /**
    * Public static method that create the JSON file. Use this method for
    * creating the JSON file of a note, that is going to be attached on an
    * email.
    * 
    * @param resolver
    *           The ContentResolver to get access to the vocal note.
    * @param VocalNote
    *           The URI of the vocal note to wrap into the JSON file.
    * 
    * @throws FileNotFoundException
    *            Thrown if URI is wrong or cannot write into the JSON file.
    */
   public static void createJsonFile(ContentResolver resolver, Uri vocalNote)
         throws FileNotFoundException {
      // the cursor that contains the result of the query (the vocal note
      // fields)
      Cursor result = resolver.query(vocalNote, null, null, null, null);
      ;

      // if no vocal notes result
      if (!result.moveToFirst()) {
         // then throw an exception
         throw new FileNotFoundException("The vocal note doesn't exist.");
      }

      // the JSON object needed to create the JSON string
      JSONObject json = new JSONObject();

      // temporary buffer for reading the audio file
      byte[] buffer = new byte[4096];

      // number of read bytes
      int n;

      // internal byte array as output stream into which writing the
      // base64-encoded bytes
      // it is used because direct converting the buffer into a string and
      // concatenating every resulting string will cause wrong encoding
      // (initial size of the internal byte array: 500KB)
      ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(
            1024 * 500);

      // the base64-encoded that writes resulting bytes into the internal byte
      // array output stream
      Base64OutputStream encoder = new Base64OutputStream(byteOutputStream,
            Base64.NO_CLOSE);

      // string that store the base64-encoded bytes of the audio file
      String encodedAudioString;

      // inputStream to read the audio file from
      InputStream is;

      // outputStream to write the JSON file to
      OutputStream os;

      try {
         // open the input stream of the audio file
         is = resolver.openInputStream(vocalNote);

         // read from the input stream using the buffer
         // until the End Of File is not reached
         while ((n = is.read(buffer)) != -1) {
            // do the base64 encoding and write into the output stream
            encoder.write(buffer, 0, n);
         }
         // close the output stream
         encoder.close();

         // get the string representation of the base64-encoded audio
         encodedAudioString = byteOutputStream.toString();
         // now encodedAudioString contain the entire base64-encoded audio file

         // close the input stream
         is.close();

         // put the identifier for this application to the JSON object
         json.put(OBJECT_APP_NAME, OBJECT_APP_NAME_VALUE);

         // put the title
         json.put(
               OBJECT_NOTE_TITLE,
               result.getString(result
                     .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE)));

         // put the date
         json.put(
               OBJECT_NOTE_DATE,
               result.getLong(result
                     .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE)));

         // put the description
         json.put(
               OBJECT_NOTE_DESCR,
               result.getString(result
                     .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION)));

         // put the rate
         json.put(
               OBJECT_NOTE_RATE,
               result.getInt(result
                     .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING)));

         // finally, put the base64-encoded audio string
         json.put(OBJECT_NOTE_AUDIO, encodedAudioString);

      } catch (FileNotFoundException f) {
         // thrown if the audio input stream cannot be opened
         throw new FileNotFoundException("The audio stream cannot be opened.");

      } catch (IOException e) {
         // thrown if errors occur while reading from or closing the input
         // stream
         e.printStackTrace();

      } catch (JSONException j) {
         // thrown if errors occur while managing the JSON object
         j.printStackTrace();
      }

      // Open the JSON file
      try {
         // open the JSON file output stream
         os = resolver.openOutputStream(getJsonURI(resolver));

         // write the JSON object string (with indent space of 3)
         os.write(json.toString(3).getBytes());

         // close the output stream
         os.close();

      } catch (FileNotFoundException f) {
         // thrown if the JSON file output stream cannot be opened
         throw new FileNotFoundException(
               "JSON file output stream cannot be opened.");

      } catch (IOException e) {
         // thrown if errors occur while writing to or closing the output stream
         e.printStackTrace();

      } catch (JSONException j) {
         // thrown if errors occur while getting the string representation
         // of the JSON object
         j.printStackTrace();
      }
   }

   /**
    * Public static method that open a JSON file. Use this method for importing
    * a JSON file of a vocal note. It controls if the file was designed for this
    * application, throwing an exception if it's not.
    * 
    * @param resolver
    *           The ContentResolver to get access to the JSON file.
    * @param jsonFile
    *           The URI of the JSON file to open.
    * @return The JSONObject of the JSON file.
    * 
    * @throws FileNotFoundException
    *            Thrown if the JSON file cannot be opened.
    * @throws IllegalArgumentException
    *            Thrown if the JSON isn't compatible.
    */
   public static JSONObject openJSONFile(ContentResolver resolver, Uri jsonFile)
         throws IllegalArgumentException, FileNotFoundException {
      // the JSON object to return and where to put the content of the JSON file
      JSONObject json = null;

      // the inputStream to read the JSON file
      InputStream is;

      // buffer for reading the JSON file
      byte[] buffer = new byte[4096];

      // number of read bytes
      int n;

      // string to accumulate the content of the JSON file
      String jsonString = new String();

      // string that will contain the value of the identifier field
      // it is used to check if this JSON file is compatible with this
      // application
      String app;

      try {
         // open the JSON file input stream
         is = resolver.openInputStream(jsonFile);

         // read from the input stream using the buffer
         // until the End Of File is not reached
         while ((n = is.read(buffer)) != -1) {
            // concatenate to the already read JSON string
            jsonString += new String(buffer, 0, n);
         }
         // now jsonString contains the entire JSON file

         // construct the JSON object from the string
         json = new JSONObject(jsonString);

      } catch (FileNotFoundException f) {
         // thrown if the JSON file input stream cannot be opened
         throw new FileNotFoundException("The JSON file cannot be opened.");

      } catch (IOException e) {
         // thrown if errors occur while reading from or closing the input
         // stream
         e.printStackTrace();

      } catch (JSONException j) {
         // thrown if errors occur while getting the string representation
         // of the JSON object
         j.printStackTrace();
      }

      // check if the JSON is compatible with this application

      try {
         // get the identifier field
         app = json.getString(OBJECT_APP_NAME);

         // if it doesn't contain the correct value of the identifier field
         if (!app.equals(OBJECT_APP_NAME_VALUE)) {
            // then throw an exception
            throw new IllegalArgumentException(
                  "The JSON file is not a vocal note.");
         }

      } catch (JSONException j) {
         // thrown if the JSON object doesn't contain the identifier field
         throw new IllegalArgumentException(
               "The JSON file is not a vocal note.");
      }

      // return the JSON object
      return json;
   }
}
